/**
 * Created by spatra on 14-9-8.
 */

define(['app/entity', 'lib/mathhelper', 'lib/physicshelper'], function(Entity, MathHelper, PhysicsHelper){

	/**
	 * PhysicsEngine的构造函数.
	 *
	 * @param canvasWidth 画布的宽度
	 * @param canvasHeight 画布的高度
	 * @param gridSize 画布被划分成一个个的正方形格子，此参数设定格子的边长
	 * @param friction 模拟摩擦力作用的系数
	 * @param gravity 模拟重力作用的系数
	 *
	 * @constructor
	 */
	var PhySicsEngine = function(canvasWidth, canvasHeight, gridSize, friction, gravity){
		this.canvasWidth = canvasWidth;
		this.canvasHeight = canvasHeight;

		this.grids = [];
		this.gridSize = gridSize;
		this.xGrids = (this.canvasWidth / this.gridSize >> 0) + 2;
		this.yGrids = (this.canvasHeight / this.gridSize >> 0) + 2;

		this.friction = friction || 0.9;
		this.gravity = gravity || 0;
	};

	PhySicsEngine.prototype = {
		constructor: PhySicsEngine,

		/**
		 * 依据参数(xGrids, yGrids)构造分格数组grids
		 *
		 * @private
		 */
		_makeGrids: function(){
			for(var i = 0; i < this.xGrids; ++i ){
				this.grids[i] = [];

				for(var j = 0; j < this.yGrids; ++j ){
					this.grids[i][j] = [];
				}
			}
		},

		/**
		 * 返回实体所在格子的纵坐标和横坐标.
		 *
		 * @param theEntity  待求坐标的实体
		 * @returns {{x: number, y: number}} 实体所在格子的坐标值
		 * @private
		 */
		_getGridsPosition: function(theEntity){
			var x, y;

			x = theEntity.x / this.gridSize >> 0;
			y = theEntity.y / this.gridSize >> 0;

			return {
				x: x,
				y: y
			}
		},

		/**
		 * 将实体放到对应的格子中去.
		 *
		 * @param entities 实体组成的数组
		 * @private
		 */
		_initGrids: function(entities){
			var gridPosition;

			for(var i = 0 ; i != entities.getLength(); ++i ){
				gridPosition = this._getGridsPosition( entities.get(i) );
				this.grids[ gridPosition.x ][ gridPosition.y ].push( entities.get(i) );
			}
		},

		/**
		 * 检查在一个格子中，是否有实体出现了边界重叠（碰撞）
		 *
		 * @param gridX 待检查的格子的横向坐标
		 * @param gridY 带检查的格子的纵向坐标
		 * @param bumpedEntities 检测到碰撞的实体所构造的数组
		 * @private
		 */
		_checkOneGrid: function(gridX, gridY, bumpedEntities){
			var theGrid = this.grids[gridX][gridY];

			var entityA, entityB;

			for(var i = 0 ; i != theGrid.length; ++i ){
				entityA = theGrid[i];
				for(var j = i + 1; j < theGrid.length; ++j ){
					entityB = theGrid[j];

					this._checkTwoEntity(entityA, entityB, bumpedEntities);
				}
			}
		},

		/**
		 * 检查两个实体是否发生了碰撞.
		 *
		 * @param entityA
		 * @param entityB
		 * @param bumpedEntities 检测到碰撞的实体所构造的数组
		 * @private
		 */
		_checkTwoEntity: function(entityA, entityB, bumpedEntities){
			if( Entity.checkCollision(entityA, entityB) ){
				entityA.collision = true;
				entityB.collision = true;

				bumpedEntities.push({
					entityA: entityA,
					entityB: entityB
				});
			}
		},

		/**
		 * 检查两个格子之间是否有实体发生了碰撞.
		 *
		 * @param gridX1 第一个格子的坐标参数，下同.
		 * @param gridY1
		 * @param gridX2 第二个格子的坐标参数，下同.
		 * @param gridY2
		 * @param bumpedEntities 检测到碰撞的实体所构造的数组
		 * @private
		 */
		_checkTwoGrid: function(gridX1, gridY1, gridX2, gridY2, bumpedEntities){

			if( gridX2 < 0 ||
				gridX2 > this.grids.length ||
				typeof(this.grids[gridX2]) == 'undefined'||
				typeof(this.grids[gridX2][gridY2]) == 'undefined' ||
				gridY2 >= this.grids[gridX2].length ) {

				return;

			}

			var gridA = this.grids[gridX1][gridY1];
			var gridB = this.grids[gridX2][gridY2];

			for( var i = 0; i != gridA.length; ++i ){
				var entityA = gridA[i];

				for(var j = 0; j != gridB.length; ++j ){
					var entityB = gridB[j];

					this._checkTwoEntity(entityA, entityB, bumpedEntities);
				}
			}
		},

		/**
		 * 检查内部所有格子中的实体是否发生了碰撞，并返回结果数组.
		 *
		 * @param entities
		 * @returns {Array} 结果数组，元素为对象{entityA, entityB}表示相撞的两个实体
		 */
		checkCollision: function(entities){
			this._makeGrids();
			this._initGrids(entities);

			var bumpedEntities = [];

			for(var i = 0 ; i != this.grids.length; ++i ){
				for(var j = 0 ; j != this.grids[i].length; ++j){
					this._checkOneGrid(i, j, bumpedEntities);
					this._checkTwoGrid(i, j, i, j + 1, bumpedEntities);
					this._checkTwoGrid(i, j, i + 1, j, bumpedEntities);
					this._checkTwoGrid(i, j, i-1, j +1, bumpedEntities);
					this._checkTwoGrid(i, j, i + 1, j + 1, bumpedEntities);
				}
			}

			return bumpedEntities;
		},

		/**
		 * 计算在当前循环中实体的下一个位置
		 * @param entities
		 */
		calculateNewPosition: function (entities) {
			var newX, newY;

			for(var i = 0; i < entities.getLength(); ++i ){
				var currentEntity = entities.get(i);

				if( currentEntity.isSkipMove() ){
					continue;
				}

				newX = currentEntity.x + currentEntity.vx + 0.5 * currentEntity.ax;
				currentEntity.vx += currentEntity.ax;

				newY = currentEntity.y + currentEntity.vy + 0.5 * currentEntity.ay;
				currentEntity.vy += currentEntity.ay;

				this._checkBoundary(newX, newY, currentEntity);
			}
		},

		/**
		 * 检查在移动实体时是否遇到了边界，并做出相应的处理。
		 *
		 * @param newX 实体的新坐标x
		 * @param newY 实体的新坐标y
		 * @param theEntity 实体
		 *
		 * @private
		 */
		_checkBoundary: function(newX, newY, theEntity){
			var radius = theEntity.radius;
			var minV = 0.2, minDeltaPos = 2;

			if( newX + radius >= this.canvasWidth || newX - radius <= 0 ){

				if( theEntity.ax !== 0 && Math.abs(theEntity.vx) < minV ){
					if( theEntity.ax > 0 && Math.abs(newX + radius - this.canvasWidth) < minDeltaPos ){
						theEntity.vx = 0;
						theEntity.x = this.canvasWidth - radius;
					}

					if( theEntity.ay < 0 && Math.abs(newX - radius) < minDeltaPos ){
						theEntity.vx = 0;
						theEntity.x = 0;
					}
				}
				else{
					theEntity.vx = -1 * theEntity.vx * this.friction;
					theEntity.x = newX + radius >= this.canvasWidth ? this.canvasWidth - radius : 0 + radius;
				}

			}
			else{
				theEntity.x = newX;
			}

			if( newY + radius >= this.canvasHeight || newY - radius <= 0 ){

				if( theEntity.ay !== 0 && Math.abs(theEntity.vy) < minV ){

					if( theEntity.ay > 0 && Math.abs(newY + radius - this.canvasHeight) < minDeltaPos ){
						theEntity.vy = 0;
						theEntity.y = this.canvasHeight - radius;
					}

					if( theEntity.ay < 0 && (newY - radius) < minDeltaPos ){
						theEntity.vy = 0;
						theEntity.y = 0;
					}

				}
				else{
					theEntity.vy = -1 * theEntity.vy * this.friction;
					theEntity.y = newY + radius >= this.canvasHeight ? this.canvasHeight - radius : 0 + radius;
				}

			}
			else{
				theEntity.y = newY;
			}
		},

		/**
		 * 解决碰撞
		 *
		 * @param entities
		 */
		resolveCollision: function(bumpedEntities){
			for(var i = 0; i != bumpedEntities.length; ++i ) {
				var entityA = bumpedEntities[i].entityA;
				var entityB = bumpedEntities[i].entityB;

				var dx = entityB.x - entityA.x;
				var dy = entityB.y - entityA.y;

				var delta = Math.atan2( dy, dx );
				delta = MathHelper.formatFloat(delta, 3);
				var deltaCos = Math.cos(delta), deltaSin = Math.sin(delta);

				var posXA = 0, posYA = 0;
				var posXB = dx * deltaCos + dy * deltaSin,
					posYB = dy * deltaCos - dx * deltaSin;

				var velXA = entityA.vx * deltaCos + entityA.vy * deltaSin;
				var velYA = entityA.vy * deltaCos - entityA.vx * deltaSin;
				var velXB = entityB.vx * deltaCos + entityB.vy * deltaSin;
				var velYB = entityB.vy * deltaCos - entityB.vx * deltaSin;

				/*
				if( velXA <= 0 && velXB <= 0 ){
					continue;
				}*/

				var result = PhysicsHelper.conservationOfEnergyAndMomentum({
					v1: velXA,
					v2: velXB,
					m1: 1,
					m2: 1
				});

				//posXA += result.v1Fin;
				//posXB += result.v2Fin;

				var overlap = entityA.radius + entityB.radius - Math.abs(posXA - posXB);

				if( velXA / velXB < 0 ){
					if( posXA > posXB ){
						posXA += overlap / 2 + 1;
						posXB -= overlap / 2 + 1;
					}
					else{
						posXA -= overlap / 2 + 1;
						posXB += overlap / 2 + 1;
					}

				}
				else{

					if( posXA > posXB ){
						posXA += overlap + 1;
					}
					else{
						posXB += overlap + 1;
					}
				}

				posXA = posXA * deltaCos - posYA * deltaSin;
				posYA = posYA * deltaCos + posXA * deltaSin;
				posXB = posXB * deltaCos - posYB * deltaSin;
				posYB = posYB * deltaCos + posXB * deltaSin;

				entityA.x = posXA + entityA.x;
				entityA.y = posYA + entityA.y;
				entityB.x = posXB + entityA.x;
				entityB.y = posYB + entityA.y;

				velXA = result.v1Fin * deltaCos - velYA * deltaSin;
				velYA = velYA  * deltaCos + result.v1Fin * deltaSin;
				velXB = result.v2Fin * deltaCos - velYB * deltaSin;
				velYB = velYB * deltaCos + result.v2Fin * deltaSin;

				entityA.vx = velXA * this.friction;
				entityA.vy = velYA * this.friction;
				entityB.vx = velXB * this.friction;
				entityB.vy = velYB * this.friction;

			}
		},

		/**
		 * 对实体的某个方向应用重力，默认是ay.
		 *
		 * @param entities
		 * @param accv
		 */
		applyGravity: function(entities, accv){
			accv = accv || 'ay';

			if( this.gravity ){
				for(var i = 0 ; i < entities.getLength(); ++i ){
					(entities.get(i))[accv] += this.gravity;
				}
			}
		},

		init: function(entities, callBack){
			if( callBack ){
				callBack.call(this, entities);
			}
			else{
				this.applyGravity(entities);
			}
		},

		run: function(entities, step){
			step = step || 1;

			for( var s = 0; s < step; ++s ){
					this.calculateNewPosition(entities);
					var bumpEntities = this.checkCollision(entities);
					this.resolveCollision(bumpEntities);
				}
		}
	};

	return PhySicsEngine;
});
